/*
 * 本类作用：程序启动入口，基于Java NIO实现数据接收（上一步：无，下一步：业务数据解析Data_GpsCeshi）
 * 
 * 作者：袁小杰
 * 博客：http://blog.csdn.net/undoner
 * GIT：https://git.oschina.net/undoner
 * QQ：15137281
 * 
 */
package com.yxj.main;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.List;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import com.yxj.analyse.Data_GpsCeshi;
import com.yxj.db.DbProperty;
import com.yxj.util.DataOutput;
import com.yxj.util.DataTranslate;

public class DataRecMain extends JFrame {
	/**
	 * 程序入口：
	 */
	private static final long serialVersionUID = -630689787107665285L;
	private JTextField tfPort;//socket端口号
	private static JTextArea textArea ;//屏幕显示区域
	private JButton btn_ok;//确认按钮
	private JButton btn_cancel;//取消按钮
	private JButton btn_log;//查看日志
	private JButton btn_clear;//清楚屏幕
	/**
	 * Selector 极大改善多个 Java Socket的效率。
	 * 在没有NIO的时候，轮询多个socket是通过read阻塞来完成，即使是非阻塞模式，我们在轮询socket是否就绪的时候依然需要使用系统调用。
	 * 而Selector的出现，把就绪选择交给了操作系统（我们熟知的select函数），把就绪判断和读取数据分开，不仅性能上大有改善，而且使得代码上更加清晰。 
	 */
	private Selector selector;
	private ServerSocketChannel serverChannel=null;
	private  Thread t_DataManager=null;//DataManager线程类
	public static  List addressLists;//终端列表显示区域
	public static ArrayList <String> socketsLists=new ArrayList<String>();//终端列表集合
	/**
	 * 自增线程池,创建一个可根据需要创建新线程的线程池，但是在以前构造的线程可用时将重用它们。
	 * 对于执行很多短期异步任务的程序而言，这些线程池通常可提高程序性能。
	 */
	public ExecutorService pool = Executors.newCachedThreadPool();
	private JPanel jPane;//创建具有双缓冲和流布局的新 JPanel
	public JFrame jFrame;//构造一个初始时不可见的新窗体
	static DataRecMain dataRecMain;

	private String gpsflag = "";//标志是否有效GPS数据
	/**
	 * 程序入口：main主函数
	 */
	public static void main(String[] args) {	
		DbProperty.getInitDB();
		EventQueue.invokeLater(new Runnable() {		
			//runnable 的 run 方法在 the system EventQueue 的指派线程中被调用。
			public void run() {
				try {
					dataRecMain = new DataRecMain();	//声明实例化一个frame程序主窗口
					//new DataReloadTimer(10);
					//Timer时间类，实现定时从数据库中读取相关配置信息，参数为秒
				} catch (Exception e) {
					StackTraceElement[] stacks = new Throwable().getStackTrace();  
			        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
			        DataOutput.print(error +":"+ e.getMessage(), 2);
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public DataRecMain() {

		jPane=new JPanel(new BorderLayout()); 
		//清屏按钮
		btn_clear = new JButton("清除屏幕");
		btn_clear.setEnabled(true);
		//端口输入框
		tfPort = new JTextField();
		tfPort.setHorizontalAlignment(SwingConstants.CENTER);
		tfPort.setBackground(Color.CYAN);
		tfPort.setToolTipText("输入服务监听端口：");
		tfPort.setColumns(7);
		if(System.getProperty("localport")!=null)
		{
			tfPort.setText(System.getProperty("localport"));
		}
		else
		{
			tfPort.setText("3333");
		}
		//接收数据按钮
		btn_ok = new JButton("接收数据");	//接收数据
		btn_ok.addMouseListener(new MouseAdapter() {	//按钮事件
			@Override
			public void mouseClicked(MouseEvent e) {
				if(btn_ok.isEnabled())
				{
					setNowInfo();
				}
			}
		});
		//停止接收按钮
		btn_cancel = new JButton("停止接收");		//停止接收
		btn_cancel.addMouseListener(new MouseAdapter() {//按钮事件
			@Override
			public void mouseClicked(MouseEvent e) {
				if(btn_cancel.isEnabled())
				{
					try{
						t_DataManager.interrupt();
					}catch(Exception ex)
					{
						StackTraceElement[] stacks = new Throwable().getStackTrace();  
				        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
				        DataOutput.print(error +":"+ ex.getMessage(), 2);
						btn_ok.setEnabled(true);
					}
					//textArea.append("\n"+"停止接收数据......");
					DataOutput.print("停止接收数据......",1);
					btn_ok.setEnabled(true);
					btn_cancel.setEnabled(false);
				}
			}
		});
		btn_cancel.setEnabled(false);
		btn_log = new JButton("查看日志");		//查看日志按钮
		btn_log.addMouseListener(new MouseAdapter() {		//按钮事件
			@Override
			public void mouseClicked(MouseEvent e) {
				if(btn_log.isEnabled())
				{
					try{
						String url =System.getProperty("user.dir")+"\\log";
						java.awt.Desktop.getDesktop().open(new File(url)); 
					}catch(Exception ex)
					{
						StackTraceElement[] stacks = new Throwable().getStackTrace();  
				        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
				        DataOutput.print(error +":"+ ex.getMessage(), 2);

					}
				}
			}
		});
		//button_2.setEnabled(false);

		addressLists=new List();

		//接收数据区域
		textArea = new JTextArea("", 33, 49);
		textArea.setLineWrap(true);
		textArea.setEditable(true);
		textArea.setToolTipText("数据接收区域");
		JScrollPane ReceiveText = new JScrollPane(textArea);// 把滚动条和多行文本框绑定
		ReceiveText.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		ReceiveText.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

		//在线终端列表
		JScrollPane li=new JScrollPane();
		li.getViewport().add(addressLists);
		//btn_clear.setEnabled(false);
		addressLists.setEnabled(true);
		//    
		//    FlowLayout f=new FlowLayout();
		//    f.setAlignment(FlowLayout.RIGHT);
		//    JPanel sendFrame=new JPanel(f);
		//    
		//    
		//connectFrame父区域
		FlowLayout flow=new FlowLayout();
		flow.setAlignment(FlowLayout.LEFT);
		JPanel connectFrame=new JPanel(flow);
		connectFrame.add(tfPort,BorderLayout.EAST);
		connectFrame.add(btn_ok,BorderLayout.EAST);
		connectFrame.add(btn_cancel,BorderLayout.EAST);
		connectFrame.add(btn_log,BorderLayout.EAST);
		connectFrame.add(btn_clear,BorderLayout.EAST);

		//messageFrame子区域：centerFrame区域
		FlowLayout flowmsg=new FlowLayout();
		flowmsg.setAlignment(FlowLayout.LEFT);
		JPanel messageFrame=new JPanel(flowmsg);

		JLabel jLabel=new JLabel("在 线 终  端",new ImageIcon(getClass().getResource("/com/sun/java/swing/plaf/windows/icons/DetailsView.gif")),JLabel.CENTER);
		//jLabel.setSize(20,40);
		messageFrame.add(ReceiveText,BorderLayout.NORTH);

		//listFrame子区域：centerFrame区域

		JPanel listFrame=new JPanel(new BorderLayout(10,10));
		listFrame.add(jLabel,BorderLayout.NORTH);
		listFrame.add(addressLists,BorderLayout.EAST);

		//centerFrame区域：lowFrame父区域
		JPanel centerFrame=new JPanel(new BorderLayout(10,10));
		centerFrame.add(messageFrame,BorderLayout.CENTER);
		centerFrame.add(listFrame,BorderLayout.WEST);

		//lowFrame父区域  	
		JPanel lowFrame=new JPanel(new BorderLayout(10,10));
		lowFrame.add(centerFrame,BorderLayout.CENTER);
		//lowFrame.add(sendFrame,BorderLayout.SOUTH);
		lowFrame.setBackground(new Color(70,163,255));

		jPane.add(connectFrame,BorderLayout.NORTH);
		jPane.add(lowFrame,BorderLayout.CENTER);  	  	

		jFrame=new JFrame("数据分析服务器");

		jFrame.getContentPane().add(jPane);   

		jFrame.setBounds(100,200,700,600);
		//jFrame.pack();
		//jFrame.setBackground(SystemColor.textHighlight);	//设置此组件的背景色)
		jFrame.setResizable(false);
		jFrame.setVisible(true);    
		jFrame.setLocationRelativeTo(null); 
		jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jFrame.setIconImage(Toolkit.getDefaultToolkit().getImage(DataRecMain.class.getResource("/com/sun/java/swing/plaf/windows/icons/Computer.gif")));	//设置要作为此窗口图标显示的图像。 
		//清屏按钮事件监听 
		btn_clear.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				textArea.setText("");
			}
		});

		//列表框双击事件监听
		addressLists.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				if(e.getClickCount()==2){
					JOptionPane.showMessageDialog(null,"断开连接！","警告对话框",JOptionPane.WARNING_MESSAGE);
				}

			}});

	}
	public static boolean isNum(String str){	//判断端口号是否为数字
		return str.matches("^[-+]?(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)$");
	}
	private void setNowInfo()	//设定端口号
	{
		try {

			String localport=tfPort.getText();
			if(localport==null||localport.equals(""))
			{
				tfPort.setText("请输入端口号");
				tfPort.setBackground(Color.YELLOW);
				tfPort.getFocusAccelerator();
				btn_ok.setEnabled(true);
				btn_cancel.setEnabled(false);
				return;
			}
			if(!isNum(localport))
			{
				tfPort.setText("");
				tfPort.setBackground(Color.YELLOW);
				tfPort.setToolTipText("端口号只能为数字");
				tfPort.getFocusAccelerator();
				btn_ok.setEnabled(true);
				btn_cancel.setEnabled(false);
				return;
			}
			if(Integer.parseInt(localport)<0||Integer.parseInt(localport)>65535)
			{
				tfPort.setText("");
				tfPort.setBackground(Color.YELLOW);
				tfPort.setToolTipText("端口范围是0~65535的数字");
				tfPort.getFocusAccelerator();
				btn_ok.setEnabled(true);
				btn_cancel.setEnabled(false);
				return;
			}
			tfPort.setBackground(Color.CYAN);
			tfPort.setToolTipText("");
			tfPort.setEnabled(false);

			
			selector = Selector.open();
			// 静态方法 实例化selector，静态工厂模式，异常处理是IOException
			serverChannel = ServerSocketChannel.open();
			//用静态的open( )工厂方法创建一个新的ServerSocketChannel对象，将会返回同一个未绑定的java.net.ServerSocket关联的通道。
			//该对等ServerSocket可以通过在返回的ServerSocketChannel上调用socket( )方法来获取。
			serverChannel.configureBlocking(false);
			// 设置为非阻塞方式,如果为true那么就为传统的阻塞方式，异常IllegalBlockingModeException
			serverChannel.socket().bind(new InetSocketAddress(Integer.valueOf(localport)));
			// 绑定IP 及端口
			serverChannel.register(selector, SelectionKey.OP_ACCEPT); 
			// 注册，四种操作 read，write，accept，connect。 
			DbProperty.getInitDB();//初始化数据库连接池
			Runnable r = new DataManager();  
			t_DataManager = new Thread(r);  
			t_DataManager.start();
			btn_ok.setEnabled(false);
			btn_cancel.setEnabled(true);
			// OP_ACCEPT事件
		} catch (IOException e) {
			StackTraceElement[] stacks = new Throwable().getStackTrace();  
	        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
	        DataOutput.print(error +":"+ e.getMessage(), 2);
		}
	}
	//用于程序界面显示区域输出
	public static void setTextArea(String info)
	{
		textArea.append("\n"+info);
	}
	//接收数据主线程t_DataManager，用以控制程序接收的开始、停止。
	class DataManager implements Runnable {		

		public void run() {
			// TODO Auto-generated method stub
			InetAddress addr;
			String ip="127.0.0.1";
			try {
				addr = InetAddress.getLocalHost();
				ip=addr.getHostAddress();//获得本机IP
				ip=String.format("%1$3s%2$3s%3$3s%4$3s", ip.split("\\.")).replaceAll(" ", "0");
				ip=ip+String.format("%05d", Integer.parseInt(tfPort.getText()));
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				StackTraceElement[] stacks = new Throwable().getStackTrace();  
		        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
		        DataOutput.print(error +":"+ e.getMessage(), 2);
			}

			//java.nio.channels.SocketChannel
			SocketChannel sc = null;
			//反复执行，等待有效的I/O操作
			while (true) {
				try {
					//t_DataManager线程未被打断
					if(!t_DataManager.isInterrupted())
					{
						//textArea.append("\n"+"服务器等待连接中...");
						selector.select();
					}
					//t_DataManager线程被打断
					else if(t_DataManager.isInterrupted())
					{  
						selector.close();
						serverChannel.close();
						tfPort.setEnabled(true);
						break;
					}
					byte[] ddd = new byte[26];
					//调用selectedKeys()方法返回一个Set实例，获取一个Iterator
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iter = keys.iterator();
					sc=null;
					String clientFlag = "";
					//创建Buffer缓冲区，默认分配1024个字节
					//					Charset charSet=Charset.forName("utf-8");
					//					CharsetEncoder encoder=charSet.newEncoder();				
					ByteBuffer echoBuffer = ByteBuffer.allocate(1024);

					//反复循环，等待I/O，调用操作器
					while (iter.hasNext()) {

						SelectionKey key = iter.next();
						// 判断键关联的信道上是否有新的socket连接
						if (key.isAcceptable()) {
							ServerSocketChannel ssc = (ServerSocketChannel) key
									.channel();

							sc = ssc.accept();
							sc.configureBlocking(false);// false，则此通道将被置于非阻塞模式 
							sc.register(selector, SelectionKey.OP_READ);

							//textArea.append("\n"+"有新连接:" + sc);
							DataOutput.print("有新连接:" + sc,1);
							//平台回应下发信息
							sc.write(ByteBuffer.wrap(("OK-connect").getBytes()));
							System.out.println(sc.socket().getInetAddress().toString().split("/")[1]);
							//socketsLists.add(sc.socket().getInetAddress().toString().split("/")[1]);
							addressLists.add(sc.socket().getInetAddress().toString().split("/")[1]);	 
						}
						// 判断键关联的信道上是否有正在等待的读操作
						else if (key.isReadable()) {

							sc = (SocketChannel) key.channel();

							clientFlag = sc.socket().getInetAddress().getHostAddress();
							//textArea.append("\n"+"收的來自 "+ sc.socket().getInetAddress().getHostName() + " 的消息： ");
							DataOutput.print("收的來自 "+ sc.socket().getInetAddress().getHostName() + " 的消息： ",1);
							echoBuffer.clear();
							int a_echoBuffer;
							//临时存放缓冲区echoBuffer中字节
							a_echoBuffer = sc.read(echoBuffer);

							if (a_echoBuffer == -1)
							{
								sc.close();
								break;
							}
							if (a_echoBuffer > 0) {
								String PhontoNo = "";
								ByteBuffer bf = ByteBuffer.allocate(26);
								byte[] b = echoBuffer.array();
								byte[] f = new byte[a_echoBuffer];
								for (int i = 0; i < a_echoBuffer; i++) {
									if (i < 15 && i > 3)
										PhontoNo += ((char) b[i]);
									f[i] = b[i];
								}
								
								String data_ASCII=DataTranslate.getASCII(f);//ASCII字符串
								//String data_Hexadecimal=DataTranslate.bytesToHexString(f);//十六进制
								//String data_Binary=DataTranslate.bytes2BinaryStr(f);//二进制

								//GPS坐标极量测试
								if (data_ASCII.startsWith("13"))
								{
									long dataID=System.currentTimeMillis();
									try{
										long start=System.currentTimeMillis();
										Data_GpsCeshi.analyseData(data_ASCII,dataID);
										long end=System.currentTimeMillis();
										System.err.println(dataID+"Analyse Cost Time:"+(end-start));
										Map map=Thread.getAllStackTraces();
										System.err.println("当前线程数："+map.size());
									}
									catch(Exception e)
									{
										Map map=Thread.getAllStackTraces();
										System.out.println(map.size());
										StackTraceElement[] stacks = new Throwable().getStackTrace();  
								        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
								        DataOutput.print(error +":"+ e.getMessage()+"map.size():"+map.size(), 2);
									}
									echoBuffer.flip();
									SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss.S");
									Date d = new Date();
									Calendar cl=Calendar.getInstance();
									String time=String.format("%1$tY%1$tm%1$te%tT", cl).replaceAll(":", "")+String.format("%03d", cl.get(Calendar.MILLISECOND));
									DataOutput.print("服务器时间："+dateFormat.format(d)+" 收到数据："+data_ASCII,1);
									//textArea.append("\n"+"服务器时间："+dateFormat.format(d)+" 收到数据："+data);

									//平台回应下发信息
									sc.write(ByteBuffer.wrap(("OK").getBytes()));
								}

								//无效数据
								else
								{
									DataOutput.print("无效数据，未予解析！数据内容："+data_ASCII,1);
									//平台回应下发信息
									sc.write(ByteBuffer.wrap(("ERROR").getBytes()));
								}

							}
							//System.out.println(sc.toString());
							//sc.close();
							
							//System.out.println(textArea.getLineCount());
							if(textArea.getLineCount()>10000)
							{
								DataOutput.print("屏幕日志超过1万条,内容过多将自动进行清屏！历史数据请查看文件.",1);
								textArea.setText("");
							}
						}// 可读

						iter.remove();// 处理完事件的要从keys中删去
					}
				}
				catch (SocketException e) {
					
					StackTraceElement[] stacks = new Throwable().getStackTrace();  
			        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
			        DataOutput.print(error +":"+ e.getMessage(), 2);
			        
					addressLists.remove(sc.socket().getInetAddress().toString().split("/")[1]);
					try {
						sc.close();
					} catch (IOException e1) {
						StackTraceElement[] stacks1 = new Throwable().getStackTrace();  
				        String error1="class: "+stacks1[1].getClassName()+"; method: "+stacks1[1].getMethodName()+"; number: "+stacks1[1].getLineNumber();
				        DataOutput.print(error1 +":"+ e1.getMessage(), 2);
					}
					continue;
				} catch (IOException e) {
					addressLists.remove(sc.socket().getInetAddress().toString().split("/")[1]);
					
					StackTraceElement[] stacks = new Throwable().getStackTrace();  
			        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber()+"; socket:"+sc.socket().getInetAddress().toString().split("/")[1];
			        DataOutput.print(error +":"+ e.getMessage(), 2);

					try {
						sc.close();
					} catch (IOException e1) {
						StackTraceElement[] stacks1 = new Throwable().getStackTrace();  
				        String error1="class: "+stacks1[1].getClassName()+"; method: "+stacks1[1].getMethodName()+"; number: "+stacks1[1].getLineNumber();
				        DataOutput.print(error1 +":"+ e1.getMessage(), 2);
						
					}
					continue;
				}
				catch (Exception e) {
					StackTraceElement[] stacks = new Throwable().getStackTrace();  
			        String error="class: "+stacks[1].getClassName()+"; method: "+stacks[1].getMethodName()+"; number: "+stacks[1].getLineNumber();
			        DataOutput.print(error +":"+ e.getMessage(), 2);
					
				}
			}
		}
	}
}

